// 09/04/2009 Dennis
class Database implements Runnable
{
  HashMap users;
  HashMap tracks;
  HashMap artists;
  HashMap albums;
  HashMap months;
  HashMap timezones;
  
  User qryUser;
  Artist qryArtist;
  Album qryAlbum;
  Track qryTrack;
  Date minTime;
  Date maxTime;
  long qryMin = 0;
  long qryMax = 0;
  int maxPop = 0;
  boolean loaded = false;
  int numRecords;
  int loadedRecords;
  int qryIntervals = 50;
  
  public Database()
  {
    artists = new HashMap();
    albums = new HashMap();
    users = new HashMap();
    tracks = new HashMap();
    months = new HashMap();
    timezones = new HashMap();
    months.put("Jan", new Integer(1));
    months.put("Feb", 2);
    months.put("Mar", 3);
    months.put("Apr", 4);
    months.put("May", 5);
    months.put("Jun", 6);
    months.put("Jul", 7);
    months.put("Aug", 8);
    months.put("Sep", 9);
    months.put("Oct", 10);
    months.put("Nov", 11);
    months.put("Dec", 12);
  }
  
  public void run()
  {
    load();
    loaded = true;
  }
  
  // 09/04/2009 Dennis
  // loads a csv file, parses it and adds the records
  public void load()
  {
    String lines[] = loadStrings("/data/dataset.csv");
    numRecords = lines.length;
    for (int i = 0; i < lines.length; i++)
    {
      StringTokenizer st = new StringTokenizer(lines[i], ",");
      String user = st.nextToken();
      user = user.charAt(0) + user.substring(1, user.length()).toLowerCase();
      String artist = st.nextToken();
      String album = st.nextToken();
      String track = st.nextToken();
      String date = st.nextToken();
      addRecord(user, artist, album, track, date);
      loadedRecords++;
    }
  }
  
  // 09/04/2009 Dennis
  // adds a record to the database by creating all needed objects and adding them
  private void addRecord(String sUser, String sArtist, String sAlbum, String sTrack, String sDate)
  {
    User user = getUser(sUser);
    if(user == null)
    {
      user = new User(sUser);
      users.put(sUser, user);
    }
    Artist artist = getArtist(sArtist);
    if(artist == null)
    {
      artist = new Artist(sArtist);
      artists.put(sArtist, artist);
    }
    Album album = getAlbum(sAlbum);
    if(album == null)
    {
      album = new Album(sAlbum, artist);
      albums.put(sAlbum, album);
    }
    Track track = getTrack(sArtist+sTrack);
    if(track == null)
    {
      track = new Track(sTrack, artist, album);
      tracks.put(sArtist+sTrack, track);
    }
    // parsing with DateFormat works but it's SLOW!
    /*DateFormat dfm = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.ENGLISH);
    Date date = dfm.parse(sDate, new ParsePosition(0));*/
    int dateMonth = (Integer)months.get(sDate.substring(4, 7));
    int dateDay = Integer.parseInt(sDate.substring(8, 10));
    int dateHour = Integer.parseInt(sDate.substring(11, 13));
    int dateMinute = Integer.parseInt(sDate.substring(14, 16));
    // can't get the timezones to work properly...
    // that date format we are given is just stupid
    /*String dateTimezone = sDate.substring(20,23);
    int offset = 0;
    if(timezones.containsKey(dateTimezone))
    {
      offset = (Integer)timezones.get(dateTimezone);
    }
    else
    {
      DateFormat dfm = new SimpleDateFormat("z", Locale.ENGLISH);
      Date date = dfm.parse(dateTimezone, new ParsePosition(0));
      offset = (int)(date.getTime());
      timezones.put(dateTimezone, offset);
    }*/
    int dateYear = Integer.parseInt(sDate.substring(24, 28));
    Date date = new Date(dateYear - 1900, dateMonth - 1, dateDay, dateHour - 1, dateMinute - 1, 0);
    
    artist.addAlbum(album);
    album.addTrack(track);
    user.addTrack(track);
    user.addAlbum(album);
    user.addArtist(artist);
    long lDate = date.getTime();
    track.addPlayed(sUser, lDate);
    if(minTime == null || minTime.after(date))
      minTime = date;
    if(maxTime == null || maxTime.before(date))
      maxTime = date;
    if(user.minTime == 0 || user.minTime > lDate)
      user.minTime = lDate;
    if(user.maxTime == 0 || user.maxTime < lDate)
      user.maxTime = lDate;
  }
  
  // 09/04/2009 Dennis
  public User getUser(String name)
  {
    if(users.containsKey(name))
      return (User)users.get(name);
    return null;
  }
  
  // 09/04/2009 Dennis
  public Artist getArtist(String name)
  {
    if(artists.containsKey(name))
      return (Artist)artists.get(name);
    return null;
  }
  
  // 09/04/2009 Dennis
  public Album getAlbum(String name)
  {
    if(albums.containsKey(name))
      return (Album)albums.get(name);
    return null;
  }
  
  // 09/04/2009 Dennis
  public Track getTrack(String name)
  {
    if(tracks.containsKey(name))
      return (Track)tracks.get(name);
    return null;
  }
  
  // 18/04/2009 Dennis
  public ArrayList queryUsers()
  {
    ArrayList results = new ArrayList();
    Object[] keys = users.keySet().toArray();
    Arrays.sort(keys);
    for(int i = 0; i < keys.length; i++)
    {
      User user = (User)users.get((String)keys[i]);
      results.add(user);
    }
    return results;
  }
  
  // 18/04/2009 Dennis
  public ArrayList queryArtists()
  {
    ArrayList results = new ArrayList();
    Object[] keys;
    if(qryUser == null)
      keys = artists.keySet().toArray();
    else
      keys = qryUser.artists.keySet().toArray();
    Arrays.sort(keys);
    for(int i = 0; i < keys.length; i++)
    {
      Artist artist = (Artist)artists.get((String)keys[i]);
      results.add(artist);
    }
    return results;
  }
  
  // 18/04/2009 Dennis
  public ArrayList queryAlbums()
  {
    ArrayList results = new ArrayList();
    Object[] keys;
    if(qryUser == null)
    {
      if(qryArtist == null)
        keys = albums.keySet().toArray();
      else
        keys = qryArtist.albums.keySet().toArray();
    }
    else
    {
      if(qryArtist == null)
        keys = qryUser.albums.keySet().toArray();
      else
        keys = qryArtist.albums.keySet().toArray();
    }
    Arrays.sort(keys);
    for(int i = 0; i < keys.length; i++)
    {
      Album album = (Album)albums.get((String)keys[i]);
      if(qryUser != null)
      {
        if(qryUser.albums.containsKey(album.name))
          results.add(album);
      }
      else
        results.add(album);
    }
    return results;
  }
  
  // 25/04/2009 Dennis
  public ArrayList queryTracks()
  {
    ArrayList results = new ArrayList();
    HashMap hm;
    if(qryAlbum != null)
    {
      hm = qryAlbum.tracks;
    }
    else if(qryArtist != null)
    {
      Iterator it1 = qryArtist.albums.keySet().iterator();
      while(it1.hasNext())
      {
        Album a = (Album)qryArtist.albums.get(it1.next());
        Iterator it2 = a.tracks.keySet().iterator();
        while(it2.hasNext())
        {
          Track t = (Track)a.tracks.get(it2.next());
          if(qryUser == null || (qryUser != null && qryUser.tracks.containsKey(t.name)))
            results.add(t);
        }
      }
      return results;
    }
    else
      hm = tracks;
    
    Iterator it = hm.keySet().iterator();
    while(it.hasNext())
    {
      Track t = (Track)hm.get(it.next());
      if(qryUser == null || (qryUser != null && qryUser.tracks.containsKey(t.name)))
        results.add(t);
    }
    return results;
  }
  
  public ArrayList queryPopularity()
  {
    ArrayList results = new ArrayList();
    int count = qryIntervals;
    if(count <= 1) count = 2;
    ArrayList timeList = qryUser == null ? qryTrack.allUsers : (ArrayList)qryTrack.users.get(qryUser.name);
    long minT = qryMin == 0 ? minTime.getTime() : qryMin;
    long maxT = qryMax == 0 ? maxTime.getTime() : qryMax;
    long interval = (maxT - minT) / count;
    int index = 0;
    int maxVal = 0;
    long timeLimit = minT + interval;
    for(int i = 0; i < count; i++)
    {
      int value = 0;
      for(int j = index; j < timeList.size(); j++)
      {
        long time = (Long)timeList.get(j);
        if(time >= minT)
        {
          if(time < timeLimit)
          {
            value++;
            index = j + 1;
          }
          else
          {
            index = j;
            break;
          }
        }
      }
      if(value > maxVal) maxVal = value;
      results.add(value);
      timeLimit += interval;
    }
    maxPop = maxVal;
    return results;
  }
}
